<template>
	<ResizeObserver
		:class="wrapClasses()"
		:style="style"
		@resize="resizeFn"
	>
		<div
			ref="referenceRef"
			:class="classes()"
			@click="clickFn"
			@mouseenter="hover=true"
			@mouseleave="hover=false"
		>
			<slot
				v-if="prefixIcon||$slots.prefix"
				name="prefix"
				v-bind="slotScope()"
			>
				<Icon
					:name="prefixIcon"
					class="pf-Select_icon pf--prefix"
				/>
			</slot>
			<div
				v-if="multiple"
				class="pf-Select_tagContainer"
			>
				<div
					v-for="item in selectedData.selected"
					:key="item.value"
					class="pf-Select_tag"
				>{{item.tag}}<Icon
					v-if="!(disabled||item.disabled)"
					name="bi-x-lg"
					class="pf-Select_tagDelete"
					@click.stop="clickOptionFn(item)"
				/></div>
				<input
					v-if="editable()"
					ref="inputRef"
					type="text"
					:value="filterText"
					class="pf-Select_multipleInput"
					:style="{width:filterInputWidth+'px'}"
					@input="inputFn"
					@focus="focusFn"
					@blur="blurFn"
				>
				<div
					v-if="!selectedData.selected.length&&!filterText"
					class="pf-Select_hint"
				>{{showPlaceholder()}}</div>
			</div>
			<div
				v-else-if="editable()"
				class="pf-Select_inputWrap"
				:class="{'pf--empty':!selectedData.selected||visible()}"
			>
				<input
					ref="inputRef"
					type="text"
					:value="filterText"
					class="pf-Select_input"
					@input="inputFn"
					@focus="focusFn"
					@blur="blurFn"
				>
				<span :style="{visibility:filterText?'hidden':null}">{{showValue()}}</span>
			</div>
			<div
				v-else
				class="pf-Select_inputWrap"
				:class="{'pf--empty':!selectedData.selected}"
			>{{showValue()}}</div>
			<Icon
				v-if="showClearIcon()"
				key="clearIcon"
				name="bi-x-circle-fill"
				class="pf-Select_clear"
				@click.stop="withFocus(clear)"
			/>
			<slot
				v-else
				v-bind="slotScope()"
				name="arrow"
			><Icon
				:name="arrowIcon"
				class="pf-Select_arrow"
			/></slot>
		</div>
		<Floating
			v-if="floatingExist"
			ref="floatingRef"
			:reference-ref="referenceRef"
			:visible="visible()"
			:auto-compute="false"
			hide-on-click
			placement="bottom-start"
			:shift="false"
			:arrow="false"
			:padding="10"
			:append-to="appendTo"
			:appear="appearFn()"
			animation="perspective"
			@shown="$emit('shown')"
			@hidden="_hiddenFn"
			@hide-click="toggle(false)"
		><Options
			v-bind="$attrs"
			:selected-data="selectedData"
			:options="standardData.options"
			:filter-text="filterText"
			:min-width="clientWidth"
			:compute-poptip="computePoptip"
			@click="clickOptionFn"
		>
			<template #option="data">
				<slot
					name="option"
					v-bind="data"
				/>
			</template>
			<template #nodata="data">
				<slot
					name="nodata"
					v-bind="data"
				/>
			</template>
			<template #nomatch="data">
				<slot
					name="nomatch"
					v-bind="data"
				/>
			</template>
		</Options></Floating>
	</ResizeObserver>
</template>

<script setup lang="ts">
import type {
	PropOrGetter,
	FieldGetter,
	BeforeProcessor,
} from '../../../type';
import type {
	SelectShape,
	SelectOption,
	SelectRawOption,
	SelectOptionValue,
	SelectStandardOption,
	SelectSelectedData,
	SelectBeforeProcessor,
	SelectStandardData,
} from './type';
import type {
	LimitProcessor,
} from '../Floating/type';
import {
	shallowRef,
	nextTick,
	computed,
	onMounted,
	watchEffect,
} from 'vue';
import ResizeObserver from '../ResizeObserver/ResizeObserver.vue';
import Floating from '../Floating/Floating.vue';
import Options from './Options.vue';
import Icon from '../Icon/Icon';
import usePoptip from '../../../composables/usePoptip';
import {
	createGetter,
	createCache,
} from '../../../util';
import {lang} from '../../../locale';
/**
 * 选择器组件
 * @author 唐国雄
 * @date 2022/11/21
 * 属性----------------------
 * v-model				当前值
 * :shape				形状
 * :placeholder			占位文本
 * :prefix-icon			前缀图标
 * :options				选项
 * :multiple			多选
 * :emit-option			事件发射整个选项对象
 * :format				格式化
 * :label-key			显示属性
 * :value-key			值属性
 * :tag-key				标签属性(多选显示)
 * :disabled			禁用状态
 * :clearable			可清空
 * :filterable			可搜索
 * :filter-method		本地过滤方法
 * :append-to			dom插入位置
 * :nodata-text			无数据提示
 * :nomatch-text		无匹配结果提示
 * :before-select		选中前置处理器
 * :arrow-icon			箭头图标
 * :virtual-min-size	选项最小高度
 * :virtual-preload		虚拟滚动预加载量
 * :max-height			下拉菜单最大高度
 * :padding				下拉菜单上下边距
 * 事件----------------------
 * @update:modelValue	值变化
 * @select				选择
 * @unselect			取消选择
 * @clear				点击清空
 * @show				开始显示
 * @shown				显示动画完毕
 * @hide				开始隐藏
 * @hidden				隐藏动画完毕
 * 插槽----------------------
 * #prefix				前缀插槽
 * #arrow				箭头插槽
 * #option				内容插槽
 * #nodata				无数据插槽
 * #nomatch				无匹配结果插槽
 */
if(typeof cache==='function'){
	cache=cache();
}
const {ele}=cache;

const emit=defineEmits<{
	(e:'update:modelValue',val:SelectOptionValue|SelectRawOption|Array<SelectOptionValue|SelectRawOption>):void;
	(e:'select',val:SelectOptionValue|SelectRawOption):void;
	(e:'unselect',val:SelectOptionValue|SelectRawOption):void;
	(e:'clear'):void;
	(e:'show'):void;
	(e:'shown'):void;
	(e:'hide'):void;
	(e:'hidden'):void;
}>();
const props=withDefaults(defineProps<{
	class?:any;
	style?:any;
	modelValue?:any;
	shape?:SelectShape;
	placeholder?:string;
	prefixIcon?:string;
	options?:Array<SelectRawOption>;
	multiple?:boolean;
	emitOption?:boolean;
	format?:FieldGetter<string>;
	labelKey?:PropOrGetter<SelectOption>;
	valueKey?:PropOrGetter<SelectOption,SelectOptionValue>;
	tagKey?:PropOrGetter<SelectOption>;
	disabled?:boolean;
	clearable?:boolean;
	filterable?:boolean;
	beforeSelect?:BeforeProcessor<SelectBeforeProcessor>;
	appendTo?:string|HTMLElement;
	arrowIcon?:string;
}>(),{
	labelKey:'label',
	valueKey:'value',
	tagKey:'tag',
	appendTo:null,
	arrowIcon:'bi-chevron-down',
});
let clientWidth=shallowRef(0);
const inputRef=shallowRef<HTMLInputElement>(null);
const focus=shallowRef(false);
const hover=shallowRef(false);
const filterText=shallowRef<string>(null);
const filterInputWidth=shallowRef<number>(0);
//region 下拉菜单控制逻辑
const {
	referenceRef,
	floatingRef,
	visible,
	appearFn,
	floatingExist,
	hiddenFn,
	toggle,
}=usePoptip({
	emitShow(){
		emit('show');
	},
	emitHide(){
		if(filterText.value!=null&&!focus.value){
			filterText.value=null;
		}
		emit('hide');
	},
	showStatus(){
		return null;
	},
	trigger(){
		return null;
	},
	hideOnClick(){
		return true;
	},
	interactive(){
		return false;
	},
	disabled(){
		return props.disabled;
	},
	appear(){
		return false;
	},
});
function _hiddenFn(){
	hiddenFn();
	emit('hidden');
}
//endregion
//region 输入框显示值
const showValue=() => {
	const selected=selectedData.value.selected as SelectStandardOption;
	if(selected==null){
		return showPlaceholder();
	}
	return selected.label;
};
//endregion
//region placeholder
const showPlaceholder=() => {
	const {placeholder}=props;
	if(placeholder!=null){
		return placeholder;
	}
	return lang.value.Select.placeholder;
};
//endregion
//region 是否可编辑
const editable=():boolean => {
	return props.filterable&&!props.disabled;
};
//endregion
//region 是否显示, 清空按钮
const showClearIcon=(():boolean => {
	const {selected,disabledSelected}=selectedData.value;
	return props.clearable&&
		hover.value&&
		!props.disabled&&
		!(
			!filterText.value&&
			(
				selected==null||
				(
					Array.isArray(selected)&&
					selected.length===(disabledSelected?disabledSelected.length:0)
				)
			)
		);
});
//endregion
//region 类名
const classes=() => {
	const {shape}=props;
	return 'pf-Select'+
		((shape&&(shape!=='round'||!props.multiple))?` pf--shape-${props.shape}`:'')+
		(props.multiple?' pf--multiple':'')+
		(focus.value?' pf--focus':'')+
		(visible()?' pf--opened':'')+
		(props.disabled?' pf--disabled':'');
};
const wrapClasses=() => {
	return 'pf-Select_wrap'+
		(props.class?' '+props.class:'');
};
//endregion
//region 当前选中选项
const selectedData=computed<SelectSelectedData>(() => {
	const {modelValue:value,multiple,emitOption,labelKey,valueKey}=props;
	const getLabelFn=createGetter(labelKey);
	const getValueFn=createGetter(valueKey);
	const getRealValue=(raw) => {
		if(emitOption&&raw!=null&&typeof raw!=='string'&&typeof raw!=='number'){
			return getValueFn(raw)??getLabelFn(raw);
		}else{
			return raw;
		}
	};
	if(multiple){
		//region 多选
		if(Array.isArray(value)&&value.length){
			const selected=new Array(value.length);
			const disabledSelected=new Array(value.length);
			let pos=0;
			let disabledPos=0;
			const {cache}=standardData.value;
			for(let i=0;i<value.length;++i){
				const val=getRealValue(value[i]);
				if(val!=null){
					const option=cache.get(val);
					if(option!=null){
						selected[pos++]=option;
						if(option.disabled){
							disabledSelected[disabledPos++]=option;
						}
					}
				}
			}
			if(value.length!==pos){
				selected.length=pos;
			}
			if(pos){
				const result:SelectSelectedData={
					selected,
					isSelected(opt){
						return selected.indexOf(opt)>-1;
					}
				};
				if(disabledPos){
					if(disabledSelected.length!==disabledPos){
						disabledSelected.length=disabledPos;
					}
					result.disabledSelected=disabledSelected;
				}
				return result;
			}
		}
		return {
			selected:[],
			isSelected(){
				return false;
			},
		};
		//endregion
	}else{
		//region 单选
		const val=getRealValue(value);
		if(val!=null){
			const option=standardData.value.cache.get(val);
			if(option!=null){
				return {
					selected:option,
					isSelected(opt){
						return opt===option;
					},
				};
			}
		}
		return {
			selected:null,
			isSelected(){
				return false;
			},
		};
		//endregion
	}
});
//endregion
//region 标准化后的选项
const standardData=computed<SelectStandardData>(() => {
	const cache=createCache<SelectOptionValue,SelectStandardOption>();
	const {options,labelKey,valueKey,tagKey,format}=props;
	const getLabelFn=createGetter(labelKey);
	const getValueFn=createGetter(valueKey);
	const getTagFn=createGetter(tagKey);
	const newOptions=new Array(options.length);
	for(let i=0;i<newOptions.length;++i){
		const raw=options[i];
		let label:string;
		let value:SelectOptionValue;
		let tag:string;
		let disabled:boolean;
		if(typeof raw==='string'){
			label=value=tag=raw;
			disabled=false;
		}else if(typeof raw==='number'){
			value=raw;
			label=tag=raw+'';
			disabled=false;
		}else{
			label=getLabelFn(raw);
			value=getValueFn(raw)??label;
			tag=getTagFn(raw)||label;
			disabled=!!raw._disabled;
		}
		if(format){
			label=format(label);
		}
		newOptions[i]={
			label,
			value,
			tag,
			disabled,
			raw,
		};
		cache.set(value,newOptions[i]);
	}
	return {
		options:newOptions,
		cache,
	};
});
//endregion
//region 插槽作用域
const slotScope=() => {
	return {
		withFocus,
		clear,
		visible,
		toggle,
	};
};
//endregion
//region 重新计算样式
function computePoptip(limitFn?:LimitProcessor):Promise<boolean>{
	const floatingRefVal=floatingRef.value;
	if(floatingRefVal){
		return floatingRefVal.computePoptip(limitFn);
	}
	return Promise.resolve(false);
}
//endregion
//region 聚焦逻辑
function withFocus(after?:() => void):boolean{
	if(props.disabled){
		return;
	}
	after&&after();
	if(!focus.value){
		nextTick(() => {
			const inputRefVal=inputRef.value;
			inputRefVal&&inputRefVal.focus();
		});
	}
}
//endregion
//region 点击事件
function clickFn(){
	withFocus(() => {
		toggle();
	});
}
//endregion
//region 获取valueGetter
function _getValueGetter():(option:SelectStandardOption) => SelectOptionValue|SelectRawOption{
	if(props.emitOption){
		return (option) => option.raw;
	}else{
		return (option) => option.value;
	}
}
//endregion
//region 清空操作
function clear(){
	if(filterText.value){
		filterText.value='';
		return;
	}
	const {selected,disabledSelected}=selectedData.value;
	if(Array.isArray(selected)){
		const disabledLength=(disabledSelected?disabledSelected.length:0);
		if(selected.length!==disabledLength){
			emit('clear');
			if(disabledLength){
				const getValue=_getValueGetter();
				const emitVal=new Array(disabledSelected.length);
				for(let i=0;i<disabledSelected.length;++i){
					emitVal[i]=getValue(disabledSelected[i]);
				}
				emit('update:modelValue',emitVal);
			}else{
				emit('update:modelValue',[]);
			}
		}
	}else if(selected!=null){
		emit('clear');
		emit('update:modelValue',null);
	}
}
//endregion
//region 点击选项
function clickOptionFn(option:SelectStandardOption){
	withFocus(() => {
		if(option.disabled){
			return;
		}
		const {isSelected,selected}=selectedData.value;
		const multiple=Array.isArray(selected);
		let select;
		if(isSelected(option)){
			if(!multiple){
				toggle(false);
				return;
			}
			select=false;
		}else{
			select=true;
		}
		Promise.resolve(props.beforeSelect?.({
			option,
			select,
		})).then((success) => {
			if(success===false){
				throw 'beforeSelect阻止';
			}
			if(filterText.value!=null){
				filterText.value=null;
			}
			const getValue=_getValueGetter();
			if(multiple){
				if(select){
					const emitVal=new Array(selected.length+1);
					for(let i=0;i<selected.length;++i){
						emitVal[i]=getValue(selected[i]);
					}
					emitVal[selected.length]=getValue(option);
					emit('update:modelValue',emitVal);
				}else{
					const emitVal=new Array(selected.length-1);
					let j=0;
					for(let i=0;i<selected.length;++i){
						if(option!==selected[i]){
							emitVal[j++]=getValue(selected[i]);
						}
					}
					emit('update:modelValue',emitVal);
				}
			}else{
				emit('update:modelValue',getValue(option));
				toggle(false);
			}
			if(select){
				emit('select',getValue(option));
			}else{
				emit('unselect',getValue(option));
			}
		}).catch(function(){});
	});
}
//endregion
//region 值变化处理器
function inputFn(e:InputEvent){
	filterText.value=(e.target as HTMLInputElement).value;
	toggle(true);
}
//endregion
//region 聚焦,失焦处理器
function focusFn(){
	focus.value=true;
}
function blurFn(){
	focus.value=false;
	if(filterText.value!=null&&!visible()){
		filterText.value=null;
	}
}
//endregion
//region 测量多选搜索框宽度
function measureSearchInput(){
	if(props.multiple&&editable()){
		inputRef.value.parentNode.appendChild(ele);
		ele.textContent=filterText.value;
		filterInputWidth.value=ele.offsetWidth;
	}
}
//endregion
//region 多选模式高度变化, 重新计算下拉菜单位置
let lastHeight=0;
function resizeFn({width,height}){
	if(clientWidth.value!==width){
		clientWidth.value=width;
	}
	if(lastHeight!==height){
		if(lastHeight){
			computePoptip();
		}
		lastHeight=height;
	}
}
//endregion
onMounted(() => {
	watchEffect(measureSearchInput);
});
</script>

<script lang="ts">
//region 类型声明
interface Cache{
	ele:HTMLElement;
}
//endregion
//region 全局缓存
let cache:Cache|(() => Cache)=function(){
	const ele=document.createElement('div');
	ele.className='pf-Select_multipleInputMeasure';
	return {
		ele,
	};
};
//endregion
export default {
	inheritAttrs:false,
};
</script>